

#include<string>
#include <iostream>

#include <stdio.h>
#include <stdlib.h>


#define NO_ERROR 0

#include <fstream>

#include "MyDisk.h"
#include "Defines.h"

MyDisk::MyDisk()
{
}

MyDisk::~MyDisk()
{
}


MyDisk::MyDisk(int _diskSize, int _blockSize, int _numOfNodes,bool isNew){
	if (isNew) {
		this->init((long)_blockSize*_diskSize);
		this->writeInt(_diskSize,0);
		this->writeInt(_blockSize,4);
		this->writeInt(_numOfNodes,8);
		this->writeInt(_numOfNodes,24);
	}
	this->initSB();
	if (isNew){
		this->freeListSize=0;
		this->writeInt(0,this->getBlockOffset(1));
		this->writeInt(-1,this->getBlockOffset(1)+4);
		this->writeInt(this->getBlockOffset(1)+4,12);

		this->numberOfBlocksForInodeArray=this->calcInodeArray();
		this->numberOfBlocksForFreeInodes=this->calcInodeFreeListSize();

	//	printf("node array = %d , free Node = %d\n",this->numberOfBlocksForInodeArray,this->numberOfBlocksForFreeInodes);
		this->writeInt(this->initFreeListInode(),20);
		for (int i = 2; i<this->SB.diskSize ; i++){
			if (this->isFreeBlock(i)){
			//	printf("block number %d is already free\n",i);
			}
			else{
			//	printf("block number %d is NOT free\n",i);
				this->freeDataBlock(i);
			}
		}
	}else{
		this->numberOfBlocksForInodeArray=this->calcInodeArray();
		this->numberOfBlocksForFreeInodes=this->calcInodeFreeListSize();
	}

}

void MyDisk::initSB(){
	this->SB = SuperBlock(this->readInt(0),this->readInt(4),this->readInt(8));
}





void MyDisk::init(long numOfBytes){
	char* tmp=new char('0');
	this->pFile = fopen ( DISK_NAME , "wb+" );
	fclose (this->pFile);
	for(long i=0;i<numOfBytes;i++){
		fwrite(tmp,1,1,this->pFile);

	}
	free(tmp);
}

int MyDisk::initFreeListInode(){
	int i=0;
	int offset=(this->getBlockOffset(this->numberOfBlocksForInodeArray + 1));
	for (i = 0 ; i < this->SB.numOfNodes ; i++){
		this->writeChar("0",i+offset,1);
	}
	return 0;
}

void MyDisk::updateFListPtr(long num){
	this->writeInt(num,12);//updeate the first pointer
}

int MyDisk::read(unsigned char* ans,long offset,int size){
	this->pFile = fopen ( DISK_NAME , "ab+" );
	fseek( this->pFile, offset, SEEK_SET );

	unsigned int a;
	fread(ans,1,size,this->pFile);
	memcpy(&a,ans,sizeof a);

	fclose (this->pFile);
	return 0;
}

int MyDisk::write(unsigned char* y,long offset){
	int ans=0;

	this->pFile = fopen ( DISK_NAME , "rb+" );
	fseek( this->pFile, offset, SEEK_SET );
	fwrite(y,1,sizeof y,this->pFile);

	fclose (this->pFile);
	return ans;
}

int MyDisk::writeInt(int number,long offset){

	unsigned int x = number;
	unsigned char temp[sizeof x];

	memcpy(temp,&x,sizeof x);

	this->write(temp,offset);

	return 0;
}

int MyDisk::readInt(long offset){
	int ans ;
	unsigned char* temp = (unsigned char*)malloc(5);
	unsigned int* y = (unsigned int*)malloc(4);
	this->read(temp,offset,4);
	memcpy(y,temp,sizeof temp);
	ans = *y;
	free(temp);
	free(y);
	return ans;
}

int MyDisk::writeChar(char* str,long offset,int size){
	int ans=0;
	this->pFile = fopen ( DISK_NAME , "rb+" );
	ans=fseek( this->pFile, offset, SEEK_SET );
	ans=fwrite(str,1,size,this->pFile);
	fclose (this->pFile);
	return ans;
}

int MyDisk::readChar(char* str,long offset,int size){
	int ans=0;

	this->pFile = fopen ( DISK_NAME , "ab+" );
	ans=fseek( this->pFile, offset, SEEK_SET );
	ans=ans+fread(str,1,size,this->pFile);
	fclose (this->pFile);

	return 0;
}
inline
long MyDisk::getBlockOffset(int dblockNum){
	return this->SB.blockSize*dblockNum;

}

int MyDisk::numberOfFreeBlocks(){
	return this->readInt(16);
}


int MyDisk::numberOfFreeInodes(){
	return this->readInt(24);
}

int MyDisk::writeBlock(int dblockNum, char* newdata){
	int ans=0;
	long offset=this->getBlockOffset(dblockNum);
	ans=this->writeChar(newdata,offset,this->SB.blockSize);
	return ans;
}

int MyDisk::readBlock(int dblockNum, char* buf){
	int ans=0;
	long offset=this->getBlockOffset(dblockNum);
	ans=this->readChar(buf,offset,this->SB.blockSize);
	return ans;
}


int  MyDisk::allocateDataBlock(){
	int freePtr = this->readInt(12);
	int ans = -1;
	if (this->readInt(freePtr) != -1){
		printf("Error: with the end of free list\n");
		return -1;
	}
	freePtr = freePtr - 4;
	//end of block
	if (freePtr%this->SB.blockSize == 0){
		ans = (freePtr/this->SB.blockSize);
		if (ans == 1){
			printf("\nDISK IS FULL\n");
			return -1;
		}

		freePtr = this->getBlockOffset(this->readInt(freePtr)+1)-4;//ptr to the end of the next block
	//	printf("the offset of the next freePointerBlock (%d , %d, %d )\n",freePtr, freePtr/this->SB.blockSize,freePtr%this->SB.blockSize);
		this->writeInt(-1,freePtr);
		this->writeInt(freePtr, 12);
		this->freeListSize--;
		this->writeInt(this->readInt(16) - 1,16);//update size
		return ans;
	}
	else{

		ans=this->readInt(freePtr);
		this->writeInt(-1,freePtr);//sign end of list
		this->writeInt(freePtr,12);//set the pointer
		this->freeListSize--;
		this->writeInt(this->readInt(16) - 1,16);//update size
		return ans;

	}
//	printf("the F pointer is for block num %d\n",freePtr);
	return 0;
}

int  MyDisk::freeDataBlock(int dblock){
	if (dblock < 1 || dblock > this->SB.diskSize){
		printf("ERROR:can not free block %d there is no such block\n",dblock);
		return -1;
	}

	int freePtr = this->readInt(12);
	int offset = this->getBlockOffset(dblock);
	int ans=0;
	if (this->readInt(freePtr) != -1){
		printf("Error: with the end of free list %d %d\n",this->readInt(freePtr),freePtr);
		return -1;
	}
	if (freePtr%this->SB.blockSize == (this->SB.blockSize - 4)){// last word
		this->writeInt(dblock,freePtr);
		this->writeInt(freePtr/this->SB.blockSize,offset);//the futher block
		freePtr=offset+4;
		this->writeInt(-1,freePtr);//end of the list

	}
	else{

		this->writeInt(dblock,freePtr);//write in the free list
		freePtr=freePtr+4;
		this->writeInt(-1,freePtr);//end of the list

	}
	this->writeInt(freePtr,12);//write in the free list
	this->freeListSize++;
	this->writeInt(this->readInt(16)+1,16);//update size
	return freePtr;
}


int MyDisk::allocateInode(){
	if (this->readInt(24) > 0 ){//size >0

		int i=0;
		int offset=(this->getBlockOffset(this->numberOfBlocksForInodeArray + 1));
		for (i = 0 ; i < this->SB.numOfNodes ; i++){
			if (this->isFreeInode(i)){
				this->writeChar("1",i+offset,1);
				////
				char* tmp =(char*)malloc(1);
				this->readChar(tmp,i+offset,1);

				///
				this->writeInt(this->readInt(24)-1,24);
				this->initInode(i);
		//		printf("read the alloc %d %c\n",i+offset,tmp[0]);
				free(tmp);
				return i;
			}
		}
	}
	else{
		printf("Error:no more Inodes\n");
		return -1;
	}


}

int MyDisk::addInodeToFreeList(int inode){
//	printf("debug:addInodeToFreeList\n");
	int freePtr = this->readInt(20);//get the next inode
	int iNode = this->readInt(freePtr);
	if (iNode != -1){
		printf("Error: with the end of free list I node\n");
		return -1;
	}
	else{
		if ((freePtr+4)%this->SB.blockSize == 0){//the last word in the block
			this->writeInt((freePtr+4)/this->SB.blockSize,freePtr);//write pointer to next block
			this->writeInt(freePtr/this->SB.blockSize,freePtr+4);//write pointer to futher block
			this->writeInt(this->getInode(inode),freePtr+8);//write the i node
			freePtr = freePtr+8;//inc
			}


		else{
			this->writeInt(this->getInode(inode),freePtr);//writing the inode in the list
		}
		freePtr = freePtr + 4;
		this->writeInt(-1,freePtr );//end of list
		this->writeInt(freePtr,20);//update the pointer
		int size = this->readInt(24);
//		printf("update size %d\n",size +1);
		this->writeInt(size + 1,24);//update the pointer
		return inode;
	}
}


void MyDisk::initInode(int inode){
	char* n = "N";
	char* zero = "0";
	this->setInodeType(inode,n);
//	printf("initInode %d %c\n",inode,this->getInodeType(inode));
	this->setInodeHlink(inode,zero);
	this->setFileSize(inode,0);

//	printf("Debug:inint inode\n");
	for (int i=0; i < 11 ; i++){
		this->setDataBlock(inode,i,-1);
	}
}

void  MyDisk::sprintb(char *s, unsigned n) {
     unsigned mask = ~(~0u >> 1);

     while (mask) {
         *s++ = n & mask ? '1' : '0';
         mask >>= 1;
     }
     *s = '\0';
 }


bool MyDisk::isFreeBlock(int dblock){
	//the place of the iNode Array and is free list
	if (  dblock < this->numberOfBlocksForFreeInodes+(this->numberOfBlocksForInodeArray+ 1) ) return true;
	int ptr;
	int next=0;
	ptr=this->readInt(12);
	ptr=this->getBlockOffset(1);
	for (ptr=this->readInt(12);ptr  > 62 ;ptr = ptr -4 ){
		if (ptr/this->SB.blockSize == dblock) return true;
		next=this->readInt(ptr);
		if (ptr%this->SB.blockSize==0){//start of block
			if (next ==0){

				return false;
				}
				else{

					if (next == dblock ) return true;
					ptr=this->getBlockOffset(next+1);
				}
			}
			else {
				if (next == dblock ) return true;
			}
	}
}



int MyDisk::getInode(int index){
	return (index*50)+this->getBlockOffset(2);
}

int MyDisk::getInodeIndex(int offset){
	return (offset-this->getBlockOffset(2))/50;
}


int MyDisk:: blockToAddress(int block){
	return block*this->SB.blockSize;
}

int MyDisk::addressToBlock(int address){
	return (address/this->SB.blockSize);
}

bool MyDisk::isFreeInode(int inode){
	char * tmp = (char*)malloc(1);
	this->readChar(tmp,this->inodeToFreeAddress(inode),1);

	if (tmp[0] == '0') {free(tmp);return true;}
	if (tmp[0] == '1' ) {free(tmp); return false;}
	else {

		printf("Error:isFreeInode %d,%d read %c\n",inode,this->inodeToFreeAddress(inode),tmp[0]);
		free(tmp);
		return false;
	}
}



int MyDisk::freeInode(int inode){
	int numberOfInode = this->readInt(8);
	this->writeChar("0",this->inodeToFreeAddress(inode),1);
	this->writeInt(this->readInt(24)+1,24);//update size
	return inode;

}

//////////////&&&&&&&&&&&&&&&&&////////////////////////////////
int MyDisk::deleteInode(int inode){
	int numberOfInode = this->readInt(8);
	if (inode >= numberOfInode || 0 > inode || this->isFreeInode(inode) ){
		return -1;
	}
	else{
		int i;
		bool flag = true;
		int curBlock;
		for (i = 0 ; i < 10 && flag ; i++ ){
			curBlock = this->getDataBlock(inode,i);
			if (curBlock == -1){
				flag = false;
			}
			else{
			//	printf("freeing block = %d\n",curBlock);
				this->freeDataBlock(curBlock);
			}
		}
		if (flag){//all direct blocks were used and erased
			int indirect= this->getDataBlock(inode,10);
			if (indirect > 0){
				this->deleteInDirectBlock(indirect);
			}
		}

		this->initInode(inode);
		char * d= (char*)malloc(1);
		d[0]='D';
		this->setInodeType(inode ,d );
		free(d);
		return inode;

	}
}
/////////////////////////////////////////////////////
int MyDisk::setInodeType(int index,char* c){
	int numberOfInode = this->readInt(8);
	//check if this inode is exicts
//	if (index >= numberOfInode || 0 > index || this->isFreeInode(index) ){
//		return -1;
//	}
//	else{
		this->writeChar(c,this->getInode(index),1);
		return index;
//	}
}

char MyDisk::getInodeType(int index){
	int numberOfInode = this->readInt(8);
	if (index >= numberOfInode || 0 > index /*||this->isFreeInode(index)*/ ){
		return 'N';
	}
	else{
		char* ans;
		ans=(char*)malloc(1);
		this->readChar(ans,this->getInode(index),1);
		char a= ans[0];
		delete(ans);
		return a;
	}
	return 'N';
}

//write inode Number of hardLink
int MyDisk::setInodeHlink(int index,char* c){
	int numberOfInode = this->readInt(8);
	//check if this inode is exicts
	if (index >= numberOfInode || 0 > index /*|| this->isFreeInode(index)*/ ){
		return -1;
	}
	else{
		this->writeChar(c,this->getInode(index)+1,1);
		return index;
	}
}

	//read inode number of hardLinks
char MyDisk::getInodeHlink(int index){
	int numberOfInode = this->readInt(8);
		if (index >= numberOfInode || 0 > index  /*this->isFreeInode(index)*/ ){
			return 'N';
		}
		else{
			char* ans;
			ans=(char*)malloc(1);
			this->readChar(ans,this->getInode(index)+1,1);
			char a= ans[0];
			delete(ans);

			return a;
		}
		return 'N';
}

//write inode size of file
int MyDisk::setFileSize(int index,int size){
	int numberOfInode = this->readInt(8);
	//check if this inode is exicts
	if (index >= numberOfInode || 0 > index  /*this->isFreeInode(index)*/ ){
		return -1;
	}
	else{
		this->writeInt(size,this->getInode(index)+2);
		return index;
	}
}

//read inode size of file
int MyDisk::getFileSize(int index){
	int ans = -1;
	int numberOfInode = this->readInt(8);
//	if (index >= numberOfInode || 0 > index || this->isFreeInode(index) ){
//		return ans ;
//	}
//	else{

		ans = this->readInt(this->getInode(index)+2);
//	}
	return ans;
}


int MyDisk::setDataBlock(int index,int place, int dBlock){

	int addres = this->blockToAddress(dBlock);
	if (dBlock == -1) {
		addres = -1;
	}

	int numberOfInode = this->readInt(8);
	//check if this inode is exicts
	if (index >= numberOfInode || 0 > index /*this->isFreeInode(index)*/
		|| place < 0 || place > 10 ){
		return -1;
	}
	else{
		this->writeInt(addres,this->getInode(index)+6+(4*place));
		return index;
	}
}

//read inode block address at place
int MyDisk::getDataBlock(int index, int place){
	int ans = -1;
	int numberOfInode = this->readInt(8);
	if (index >= numberOfInode || 0 > index /* this->isFreeInode(index)*/
		|| place < 0 || place > 10 ){
		return -1;
	}
	else{
		ans = this->readInt(this->getInode(index)+6+(4*place));
	}
	int tmp = this->addressToBlock(ans);
	if (ans == -1){
		return -1;
	}
	return tmp;
}

int MyDisk::setInDirectDataBlock(int index,int place, int dBlock){
	int addres = this->blockToAddress(dBlock);
	int numberOfInode = this->readInt(8);
	//check if this inode is exicts
	if (index >= numberOfInode || 0 > index /* this->isFreeInode(index)*/
		|| place < 0 || place > (this->SB.blockSize/4)){
			return -1;
		}
		else{
			int inDirBlock=this->blockToAddress(this->getDataBlock(index,10));
			this->writeInt(dBlock,inDirBlock + (place*4));
			return inDirBlock;
		}
	}

//read inode block address at place
int MyDisk::getInDirectDataBlock(int index, int place){
	int numberOfInode = this->readInt(8);
	//check if this inode is exicts
	if (index >= numberOfInode || 0 > index/* this->isFreeInode(index)*/
		|| place < 0 || place > (this->SB.blockSize/4)){
		return -1;
	}
	else{
		int inDirBlock=this->blockToAddress(this->getDataBlock(index,10));
		return this->readInt(inDirBlock + (place*4));
	}
}

void MyDisk::initIndirectBlock(int dBlock){
	for (int i = 0 ; i < this->SB.blockSize ; i = i + 4){
		this->writeInt(-1,this->blockToAddress(dBlock)+i);
	}
}

int MyDisk::sizeOfIndirectBlock(int dBlock){
	int ans=0;
	for (int i=0 ; i < this->SB.blockSize ; i = i + 4 ){
		if (this->readInt(this->blockToAddress(dBlock)+i) == -1){
			ans = ans +1;
		}
	}
	return ans;
}

int MyDisk::putNextBlock(int dBlock , int newDblock){
	if (this->sizeOfIndirectBlock(dBlock) == 0){//cannot put newBlock
		return -1;
	}
	for (int i = 0; i < this->SB.blockSize ; i= i+4){
		if (this->readInt(this->blockToAddress(dBlock) + i ) == -1){
			this->writeInt(newDblock , this->blockToAddress(dBlock) + i);
			return i;
		}
	}
	return -1;
}

int MyDisk::deleteInDirectBlock(int dBlock){
	int tmp;
	for (int i = 0; i < this->SB.blockSize ; i= i+4){
		tmp = this->readInt(this->blockToAddress(dBlock) + i );
			if (tmp != -1){
				this->freeDataBlock(tmp);
			}
			else{
				break;
			}
		}

	return this->freeDataBlock(dBlock);
}



int MyDisk::calcInodeArray(){
	int tmp=this->SB.numOfNodes*50;
	if (tmp%this->SB.blockSize == 0){
		return tmp/this->SB.blockSize;
	}
	return (tmp/this->SB.blockSize) +2;
}
//returning number of blocks
int MyDisk::calcInodeFreeListSize(){
	int tmp=this->SB.numOfNodes;
	return tmp/this->SB.blockSize;
}

int MyDisk::freeInodeToInodeNumber(int ref){
	return ref - this->blockToAddress(this->numberOfBlocksForInodeArray+1);
}

int MyDisk::inodeToFreeAddress(int inode){
	return this->blockToAddress(this->numberOfBlocksForInodeArray+1) + inode;
}

/*printing methods*/

void MyDisk::printFList(){

	int ptr;
	int next=0;
	ptr=this->readInt(12);
	printf("\nthe pointer is %d\n",ptr);
	printf("and is value %d\n",this->readInt(ptr-4));
	ptr=this->getBlockOffset(1);
	bool end=false;

	for (ptr=this->readInt(12);ptr  > 62 && !end ;ptr = ptr -4 ){
		next=this->readInt(ptr);
		if (ptr%this->SB.blockSize==0){//start of block
			if (next ==0){
				printf("\nend of free list \n");
				end=true;
			}
			else{
				printf("\nthe next block is %d \n",next);
				ptr=this->getBlockOffset(next+1);
			}
		}
		else {
			printf("%d ",next);
		}

	}
}


void MyDisk::printFreeInodeList(){
	int i=0;
	char *tmp=(char*)malloc(1);
	int offset=(this->getBlockOffset(this->numberOfBlocksForInodeArray + 1));
	printf("printing FreeInodeList\n");
	printf("size is %d\n",this->readInt(24));
	for (i = 0 ; i < this->SB.numOfNodes ; i++){
		printf("%d\n",i+offset);
		this->readChar(tmp,i+offset,1);
		printf("inode number %d is %c\n",i,tmp[0]);

	}
	free(tmp);
}
void MyDisk::printSuperBlock(){
	this->SB.printMe();
	printf("pointer to free list = %d\n",this->readInt(12));
	printf("size of free list = %d\n",this->readInt(16));
	printf("offset of pointer to address of first inode = %d\n",this->readInt(20));
	printf("print the size of I Node freelist Size = %d\n",this->readInt(24));

}

void MyDisk::printInode(int inode){
	int numberOfInode = this->readInt(8);
	int i;
	printf("inode is %d\n",inode);
	if (inode >= numberOfInode || 0 > inode /*|| this->isFreeInode(inode)*/ ){
		printf("this inode is not exeicsts %d\n",inode);
	}
	else{
		printf("_____________________\n");
		printf("|inode number = %d   |\n",inode);
		printf("|type = %c           |\n",this->getInodeType(inode));
		printf("|hard links = %c     |\n",this->getInodeHlink(inode));
		printf("|size  = %d         |\n",this->getFileSize(inode));
		for (i =0;i<11;i++){
			printf("|address %d = %d     |\n",i,this->getDataBlock(inode,i));
		}
		if (this->getDataBlock(inode,10) > -1)
			this->printInDirectBlock(this->getDataBlock(inode,10));
		printf("|____________________|\n");
	}
}

void MyDisk::printInDirectBlock(int dBlock){
	printf("printing Block %d\n",dBlock);
	printf("capacity left is = %d\n",this->sizeOfIndirectBlock(dBlock));
	for (int i = 0 ; i < this->SB.blockSize ; i = i + 4){
		printf("<%d = %d>\n",i/4,this->readInt(this->blockToAddress(dBlock)+i));

		}
	}
